/*
 * Copyright 2011-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sabayframework.mem.impl;

import org.sabayframework.mem.IdGenerator;


/**
 * Class generates monotonically increasing IDs. Multiple generators can be used in 
 * a distributed system provided an unique seed for each generator. 
 * Based on Twitter's Snowflake algorithm.  
 * */
public final class SnowflakeIdGenerator implements IdGenerator {

	private static final long startTime = 1348616859157L;
	private static final long seedBits = 10L;
	private static final long maxSeed  = -1L ^ (-1L << seedBits);
	private static final long sequenceBits = 12L;

	private static final long seedShift = sequenceBits;
	private static final long timestampLeftShift  = sequenceBits + seedBits;
	private static final long sequenceMask  =  -1L ^ (-1L << sequenceBits);
	
	private final long seed;
	private long lastTimestamp = -1L;
	private long sequence;
	/**
	 * Creates new ID generator with given seed.
	 * @param seed seed is an integer number greater or equal to 0 and less than 1024. 
	 * @throws IllegalArgumentException if seed is negative number or greater than maximum seed.
	 * */
	public SnowflakeIdGenerator(int seed) {
		if (seed > maxSeed || seed < 0) {
		    throw new IllegalArgumentException(String.format("seed can't be greater than %d or less than 0",maxSeed));
		}
		this.seed = seed;
	}
	/**
	 * Generates N sequential IDs. Note that this call blocks other calls to the generator.
	 * @param n number of sequential IDs
	 * */
	public long[] getNextIds(int n){
		final long[] res = new long[n];
		synchronized (this){
			for(int i=0;i<res.length;++i) res[i] = genId();
		}
		return res;
	}
	public synchronized long getNextId(){
		return genId();
	}
	private long genId(){
		long timestamp = System.currentTimeMillis();
	    if (timestamp < lastTimestamp) {
	    	throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
	    			lastTimestamp - timestamp));
	    }
	    if (lastTimestamp == timestamp) {
	    	sequence = (sequence + 1) & sequenceMask;
	    	if (sequence == 0) timestamp = waitNextMillis(timestamp);
	    } else sequence = 0;
	    lastTimestamp = timestamp;
	    return ((timestamp - startTime) << timestampLeftShift) |
	    		(seed << seedShift) |
	    		sequence;		
	}
	
	private long waitNextMillis(long lastTimestamp) {
		long timestamp = System.currentTimeMillis();
		while (timestamp <= lastTimestamp) {
			timestamp = System.currentTimeMillis();
		}
		return timestamp;
	}
}
